package com.autonavi.yunda.yunji.core.engine.context;

import com.autonavi.yunda.yunji.common.exception.AmapException;
import com.autonavi.yunda.yunji.common.exception.AmapExceptionCode;
import com.autonavi.yunda.yunji.common.json.JSONDateSerial;
import com.autonavi.yunda.yunji.core.config.EngineConstant;
import com.autonavi.yunda.yunji.core.engine.script.InterfaceScriptClass;
import com.autonavi.yunda.yunji.core.enums.EngineType;
import com.autonavi.yunda.yunji.core.vo.node.BaseNode;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import org.apache.commons.lang3.tuple.Pair;
import org.slf4j.MDC;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

import static com.autonavi.yunda.yunji.common.constant.CommonConstant.MDC_TRACE_ID;

/**
 * @author cizhong.wcs
 * @date 2022/5/31 10:41
 */
public class EngineContext {
    @JsonIgnore
    public final InterfaceScriptClass scriptClass;
    @JsonIgnore
    public final Object scriptInstance;
    @JsonSerialize(using = JSONDateSerial.class)
    public Long startTime;
    @JsonSerialize(using = JSONDateSerial.class)
    public Long endTime;
    public Long executeTime;
    public String traceLog;
    /**
     * 0 初始化
     * -1 失败
     * 1 成功
     */
    public volatile int finishedStatus = 0;
    public final Map<String, NodeContext> nameNodeMap = new HashMap<>();
    public boolean debug = false;
    public EngineType engineType;
    @JsonIgnore
    public EngineTransactionContext transactionContext;

    public  EngineContext(InterfaceScriptClass scriptClass) {
        List<BaseNode> nodes = scriptClass.engineData.nodes;
        this.scriptClass = scriptClass;
        this.engineType = scriptClass.engineType;
        Set<String> availableNodeNames = scriptClass.nodeGraph.nameNodeMap.keySet();
        nodes.stream()
                .filter(n -> availableNodeNames.contains(n.name))
                .forEach(node -> nameNodeMap.put(
                        node.name, new NodeContext(scriptClass.nodeGraph.nameNodeMap.get(node.name), this)
                ));
        try {
            this.scriptInstance = scriptClass.newInstance();
        } catch (Exception e) {
            throw new AmapException(AmapExceptionCode.COMMON_SERVER_ERROR, "创建实例异常", e);
        }
    }

    @JsonIgnore
    public NodeContext getStartContext() {
        return nameNodeMap.get(scriptClass.nodeGraph.startNode);
    }

    public Object invoke(String functionName, Object... args) {
        return scriptClass.invoke(scriptInstance, functionName, args);
    }

    public Object invokeResponseSuccess(Object code, Object data) {
        return invoke(EngineConstant.RESPONSE_FUN_NAME, true, code, "请求成功", MDC.get(MDC_TRACE_ID), data);
    }

    public Object invokeResponseFail(Object code, String message) {
        return invoke(EngineConstant.RESPONSE_FUN_NAME, false, code, message, MDC.get(MDC_TRACE_ID), null);
    }

    public Object invokeTraceLog() {
        return invoke(EngineConstant.TRACE_LOG_FUN_NAME);
    }

    public Object invokeResponseHeaders() {
        return invoke(EngineConstant.RESPONSE_HEADERS_FUN_NAME);
    }

    public List<String> exceptionNodes() {
        return nameNodeMap.values().stream()
                .filter(nodeContext -> nodeContext.getStatus().isFail())
                .map(NodeContext::getName).collect(Collectors.toList());
    }

    public Pair<String, Throwable> fetchFirstException() {
        return nameNodeMap.values().stream()
                .filter(nodeContext -> nodeContext.getStatus().isFail())
                .findFirst().map(ctx -> Pair.of(ctx.getName(), ctx.getException()))
                .orElse(null);
    }

    public void injectParams(Map<String, Object> inputParams) {
        scriptClass.injectParams(scriptInstance, inputParams);
    }

    public void checkTimeout() {
        nameNodeMap.values().stream()
                .filter(nodeContext -> nodeContext.getStatus().isRunning())
                .forEach(nodeContext -> {
                    long timePeriod = System.currentTimeMillis() - nodeContext.getStartTime();
                    if (timePeriod > nodeContext.getNode().getNodeTimeoutMillis()) {
                        if (nodeContext.getExecuteFuture() != null) {
                            nodeContext.getExecuteFuture().cancel(true);
                            throw new AmapException(AmapExceptionCode.COMMON_SERVER_ERROR, nodeContext.getName() + "节点执行超时:" + nodeContext.getNode().getNodeTimeoutMillis() + "ms");
                        }
                    }
                });
        long timePeriod = System.currentTimeMillis() - this.startTime;
        if (timePeriod > 300000) {
            throw new AmapException(AmapExceptionCode.COMMON_SERVER_ERROR, "链路执行超时5分钟");
        }
    }

    public void handleTransactionResult() {
        if (finishedStatus == -1) {
            transactionContext.rollback();
        } else {
            transactionContext.commit();
        }
    }
}
